/*
 * Created by Jaren at 2022/2/7 10:46
 */
import 'dart:async';
import 'dart:io';

import 'package:inmotion_protocol/base/file/file_manager.dart';
import 'package:inmotion_protocol/base/protocol.dart';
import 'package:inmotion_protocol/common/exceptions.dart';
import 'package:inmotion_protocol/common/product_model.dart';
import 'package:inmotion_protocol/common/product_version.dart';
import 'package:inmotion_protocol/common/vehicle_info.dart';
import 'package:inmotion_protocol/protocol_worker.dart';
import 'package:inmotion_protocol/util/byte_util.dart';
import 'package:inmotion_protocol/util/device.dart';
import 'package:inmotion_protocol/util/log.dart';
import 'package:inmotion_protocol/util/version.dart';

abstract class FwUpdateManager {
  void config(MultiDeviceUpdateConfig config);

  Stream<FwUpdateEvent> start();

  void cancel();

  void destroy();

  /// 开始等待设备升级时间戳
  int _startWaiting4UpdateMills = 0;

  void waitThenCheckUpdateSuccess(
    int device,
    Protocol protocol,
    StreamController streamController,
    SingleDeviceUpdateConfig singleDeviceUpdateConfig,
    LinkLayerDeviceHandler linkLayerDeviceHandler,
    RequiredManualOperationHandler requiredManualOperationHandler, {
    Function? jeLiOtaCallback,
  }) {
    /// 连接设备
    /// [connectedCallback]
    /// - 回调参数1 bool 表示是否已连接
    /// - 回调参数2 String 表示当前设备ID
    /// - 回调参数3 String 表示当前设备名称
    connectLayerDevice(
      Function connectedCallback, {
      Duration scanTimeout = const Duration(seconds: 3),
      Duration connectTimeout = const Duration(seconds: 10),
      int retryTimes = 0,
      int maxRetryTimes = 3,
    }) {
      linkLayerDeviceHandler.connect((connected, deviceIdentifier, deviceName) {
        LogUtils.d(
            "${DateTime.now()} 03-22: connectedCallback of btHandler, connection state changed to connected?$connected");
        if (connected) {
          LogUtils.d("${DateTime.now()} 03-22: 连接成功 = ${connectedCallback}");
          // 连接成功
          connectedCallback(true, deviceIdentifier, deviceName);
        } else {
          LogUtils.d("${DateTime.now()} 03-22: 连接失败");
          if (retryTimes >= maxRetryTimes) {
            // 超过最大重试次数
            connectedCallback(false, deviceIdentifier, deviceName);
          } else {
            // 重试
            LogUtils.d(
                "${DateTime.now()} 03-22: connectedCallback of btHandler, retrying to reconnect after 1 second, retry times: $retryTimes, max retry times: $maxRetryTimes");
            Future.delayed(const Duration(milliseconds: 1000), () {
              connectLayerDevice(connectedCallback,
                  retryTimes: retryTimes + 1,
                  maxRetryTimes: maxRetryTimes,
                  scanTimeout: scanTimeout,
                  connectTimeout: connectTimeout);
            });
          }
        }
      }, scanTimeout: scanTimeout, connectTimeout: connectTimeout);
    }

    /// 等待设备升级
    /// 若当前更新包含连接层设备，则此过程也包括断开及重连的等待
    /// [waitingFinished] 回调参数bool，表示是否继续下一个过程
    waiting(Function waitingFinished) {
      if (singleDeviceUpdateConfig.isLinkLayerDevice ||
          requiredManualOperationHandler
              .isRequiredManualOperation(singleDeviceUpdateConfig)) {
        try {
          linkLayerDeviceHandler.disconnect();
        } catch (e) {
          LogUtils.e(
              "${DateTime.now()} 03-22: rror occurred when disconnect device.",
              e);
          // streamController.addError(FwUpdateError.byWaiting4UpdateErrorOccurred(device));
          // waitingFinished(false);
        }
      }

      /// 开始等待指定时间
      Future.delayed(
          Duration(milliseconds: singleDeviceUpdateConfig.waiting4UpdateMills),
          () {
        if (requiredManualOperationHandler
            .isRequiredManualOperation(singleDeviceUpdateConfig)) {
          /// 需要用户手动处理后再继续下一步
          /// 等待用户手动处理
          requiredManualOperationHandler.waitingOperation((shouldGoNext) {
            /// 用户手动处理完成，shouldGoNext表示是否应继续后续流程
            if (shouldGoNext) {
              LogUtils.d("${DateTime.now()} 03-22: - 点击确认");

              /// 开始连接设备
              connectLayerDevice(
                  (connected, deviceIdentifier, deviceName) async {
                LogUtils.d(
                    "${DateTime.now()} 03-22: connection changed in fw update manager, connected?$connected");
                if (connected) {
                  await protocol.prepareOnLinkLayerConnected(deviceName,
                      resetData: false);
                  waitingFinished(true);
                } else {
                  streamController.addError(
                      FwUpdateError.byWaiting4UpdateErrorOccurred(device));
                  waitingFinished(false);
                }
              });
            } else {
              /// 用户不继续后续升级流程
              /// TODO: 修改Error类型
              streamController
                  .addError(FwUpdateError.byWaiting4UpdateTimeout(device));
              waitingFinished(false);
            }
          });
        } else if (singleDeviceUpdateConfig.isLinkLayerDevice) {
          /// 连接层设备，需主动断开设备后，等待设备升级完成再进行重连，然后再查询版本确认是否升级成功
          connectLayerDevice((connected, deviceIdentifier, deviceName) {
            if (connected) {
              waitingFinished(true);
            } else {
              streamController.addError(
                  FwUpdateError.byWaiting4UpdateErrorOccurred(device));
              waitingFinished(false);
            }
          },
              scanTimeout: Duration(
                  milliseconds: singleDeviceUpdateConfig.maxWait4UpdateMills));
        } else {
          /// 非连接层设备，等待完成后直接回调
          waitingFinished(true);
        }
      }).catchError((error) {
        streamController
            .addError(FwUpdateError.byWaiting4UpdateErrorOccurred(device));
        // 不再继续，由streamController处理error
        waitingFinished(false);
      });
    }

    var queryVersionState = false;

    /// 查询版本号并检查是否成功
    queryVersion() {
      return protocol
          .requestFwVersion()
          .then((version) {
            //singleDeviceUpdateConfig.device
        // 匹配设备号，拿到对应的版本号
        Version newVersion = version.getByDevice(singleDeviceUpdateConfig.device)!;

        VersionWrapper newVersionWrapper = VersionWrapper();

        newVersionWrapper.put(
            singleDeviceUpdateConfig.device, newVersion);

        print(
            "${DateTime.now()} 03-22: 查询版本号并检查是否成功:new = $newVersionWrapper, old = ${singleDeviceUpdateConfig.versions}");
        if (isUpdatedSuccessByVersion(
                singleDeviceUpdateConfig.versions, newVersionWrapper) &&
            queryVersionState == false) {
          streamController
              .add(FwUpdateEvent.bySuccess(singleDeviceUpdateConfig.index));

          print("${DateTime.now()} 03-22: 升级成功");
          return Future.value(true);
        } else {
          if (now() - _startWaiting4UpdateMills <
              singleDeviceUpdateConfig.maxWait4UpdateMills) {
            // 继续重试
            return Future.delayed(
                Duration(
                    milliseconds: singleDeviceUpdateConfig
                        .queryVersionIntervalMills), () {
              return queryVersion();
            });
          } else {
            // 超过最大重试时间，失败
            streamController.addError(
                FwUpdateError.byCheckUpdateSuccessVersionNotMatch(device));
            return Future.value(false);
          }
        }
      }).catchError((error) {
        if (error is MsgTimeoutException) {
          if (now() - _startWaiting4UpdateMills <
              singleDeviceUpdateConfig.maxWait4UpdateMills) {
            // 继续重试
            return Future.delayed(
                Duration(
                    milliseconds: singleDeviceUpdateConfig
                        .queryVersionIntervalMills), () {
              return queryVersion();
            });
          } else {
            streamController
                .addError(FwUpdateError.byCheckUpdateSuccessTimeout(device));
            return Future.value(false);
          }
        }
        streamController
            .addError(FwUpdateError.byCheckUpdateSuccessErrorOccurred(device));
        return Future.value(false);
      });
    }

    _startWaiting4UpdateMills = now();
    waiting((shouldGoNext) {
      if (!shouldGoNext) return;

      print("${DateTime.now()} 03-22: queryVersion 1");
      if (jeLiOtaCallback != null) {
        jeLiOtaCallback();
      }
      queryVersion();
      print("${DateTime.now()} 03-22: queryVersion 2");
    });
  }

  /// 根据目标版本号和查询到的版本号确定当前更新设备是否成功
  bool isUpdatedSuccessByVersion(
      Map<int, String> targetVersions, VersionWrapper remoteVersion) {
    Iterable<int> devices = targetVersions.keys;
    for (var device in devices) {
      if (!VersionUtils.equals(targetVersions[device]!,
          remoteVersion.getAppVersionByDevice(device))) {
        return false;
      }
    }
    return true;
  }

  int now() {
    return DateTime.now().millisecondsSinceEpoch;
  }
}

class FwUpdateEvent {
  static const stepPreparing = 1;
  static const stepFileTransferring = 2;
  static const stepFileChecking = 3;
  static const stepWaiting4Update = 4;
  static const stepSuccess = 5;

  /// 当前升级的序号（顺序，用于在多设备升级中指示当前升级的index）
  int index;

  /// 升级过程中的步骤
  int step;

  FwUpdateEvent(this.index, this.step);

  FwUpdateEvent.byPreparing(int index) : this(index, stepPreparing);

  FwUpdateEvent.byFileChecking(int index) : this(index, stepFileChecking);

  FwUpdateEvent.byWaiting4Update(int index) : this(index, stepWaiting4Update);

  FwUpdateEvent.bySuccess(int index) : this(index, stepSuccess);

  @override
  String toString() {
    return "index->$index, step->$step";
  }
}

class FwUpdateFileTransferringEvent extends FwUpdateEvent {
  final FileProgressEvent _fileProgressEvent;

  FwUpdateFileTransferringEvent(int index, this._fileProgressEvent)
      : super(index, FwUpdateEvent.stepFileTransferring);

  /// 当前进度
  int progress() {
    return _fileProgressEvent.progress;
  }

  /// 总大小
  int total() {
    return _fileProgressEvent.total;
  }

  /// 平均速度
  int aveSpeed() {
    return _fileProgressEvent.aveSpeed;
  }

  /// 实时速度
  int rtSpeed() {
    return _fileProgressEvent.rtSpeed;
  }

  /// 已用时间
  int costSeconds() {
    return _fileProgressEvent.costSeconds;
  }

  /// 预计剩余时间
  int remainSeconds() {
    return _fileProgressEvent.remainSeconds;
  }

  @override
  String toString() {
    return "index->$index, step->$step, progress->${progress()}, total->${total()}, costSeconds->${costSeconds()}";
  }
}

class FwUpdatePreparingEvent extends FwUpdateEvent {
  int progress = 0;
  int total = 0;

  FwUpdatePreparingEvent.create(int index, this.progress, this.total)
      : super(index, FwUpdateEvent.stepPreparing);

  @override
  String toString() {
    return "FwUpdatePreparingEvent{index->$index, step->$step, progress->$progress, total->$total}";
  }
}

class FwUpdateError extends InMotionProtocolException {
  static const stepStartUpdate = 1;
  static const stepFileOperation = 2;
  static const stepMarkAsUpdating = 3;
  static const stepWaiting4Update = 4;
  static const stepCheckUpdateSuccess = 5;
  static const stepClearUpdatingMark = 6;
  static const stepReconnectAfterBlDownloaded = 7; // bootloader 下载完成后，重新连接时

  static const _stepDesc = [
    '',
    'Start to Update',
    'File Operating',
    'Mark as Updating',
    'Waiting for Updating',
    'Check Update Success',
    'Reconnect after bl downloaded'
  ];

  /// 操作超时
  static const errorTimeout = 0xF0;

  /// 版本号不一致
  static const errorVersionNotMatch = 0xF1;

  /// 安全连接失败
  static const errorSecurityConnection = 0xF2;

  /// 未知错误
  static const errorUnknown = 0xFF;

  int device;
  int step;
  List<int?> errors = [null, null, null, null];

  FwUpdateError(this.device, this.step);

  FwUpdateError.byStartUpdate(this.device, int error,
      {this.step = stepStartUpdate}) {
    errors[0] = error;
  }

  FwUpdateError.byFileOperation(this.device, FileOperationStepError error,
      {this.step = stepFileOperation}) {
    errors[0] = error.step;
    errors[1] = error.error;
    errors[2] = error.extra;
  }

  FwUpdateError.byWaiting4UpdateTimeout(this.device,
      {this.step = stepWaiting4Update}) {
    errors[0] = errorTimeout;
  }

  FwUpdateError.byWaiting4UpdateErrorOccurred(this.device,
      {this.step = stepWaiting4Update}) {
    errors[0] = errorUnknown;
  }

  FwUpdateError.byCheckUpdateSuccessTimeout(this.device,
      {this.step = stepCheckUpdateSuccess}) {
    errors[0] = errorTimeout;
  }

  FwUpdateError.byCheckUpdateSuccessErrorOccurred(this.device,
      {this.step = stepCheckUpdateSuccess}) {
    errors[0] = errorUnknown;
  }

  FwUpdateError.byCheckUpdateSuccessVersionNotMatch(this.device,
      {this.step = stepCheckUpdateSuccess}) {
    errors[0] = errorVersionNotMatch;
  }

  FwUpdateError.byMarkAsUpdating(this.device, int error,
      {this.step = stepMarkAsUpdating}) {
    errors[0] = error;
  }

  FwUpdateError.byClearUpdatingMark(this.device, int error,
      {this.step = stepClearUpdatingMark}) {
    errors[0] = error;
  }

  bool isNotNowError() {
    return step == stepStartUpdate && errors.isNotEmpty && errors[0] == 3;
  }

  @override
  String toString() {
    return 'FwUpdateError[${formatErrorCode('')}]';
  }

  /// 使用分隔符[split]格式化错误码
  /// 一般用于显示
  String formatErrorCode([String split = ', ']) {
    StringBuffer stringBuffer = StringBuffer(ByteUtil.byte2Hex(device));
    stringBuffer.write(split);
    stringBuffer.write(ByteUtil.byte2Hex(step));
    for (var error in errors) {
      if (error != null) {
        stringBuffer.write(split);
        stringBuffer.write(ByteUtil.byte2Hex(error));
        ByteUtil.numberLe2HexString(error);
      }
    }
    return stringBuffer.toString().toUpperCase();
  }

  String getErrorDesc() {
    StringBuffer stringBuffer = StringBuffer();
    stringBuffer.write('Device：${device.toRadixString(16)}');

    stringBuffer.write(', Step：${_stepDesc[step]}');
    stringBuffer.write(', Error：[');

    for (var error in errors) {
      if (error != null) {
        stringBuffer.write(error);
        stringBuffer.write(', ');
      }
    }
    stringBuffer.write(']');
    return stringBuffer.toString();
  }
}

/// 多设备固件更新配置信息
class MultiDeviceUpdateConfig {
  /// 包含的全部单设备固件更新配置信息
  Map<int, SingleDeviceUpdateConfig> singleConfigs = {};

  /// 当前产品是否支持标记正在升级标识
  bool supportMarkAsUpdating = false;

  /// 默认通讯设备
  int defaultCommDevice = 0;

  /// 正在请求升级的设备的设备号标识
  int? deviceRequestingUpdate;

  MultiDeviceUpdateConfig();

  MultiDeviceUpdateConfig putSingleConfig(
      int device, SingleDeviceUpdateConfig c) {
    singleConfigs[device] = c;
    return this;
  }
}

/// 单设备固件更新配置信息
class SingleDeviceUpdateConfig {
  static const _defaultRemoteUpdateFileName = "update.bin";

  static const _defaultScreenUpdateFileName = "ota.bin";

  /// 当前升级序号
  int index;

  /// 当前升级设备（通讯设备）
  int device;

  /// 设备端升级文件名(包含路径)
  String remoteFilename = _defaultRemoteUpdateFileName;
  String screenFilename = _defaultScreenUpdateFileName;

  /// 本地升级文件
  File localFile;

  /// 此升级文件包含的新固件设备号和版本号
  /// 一个固件文件中可包含多个设备的固件
  /// [key]为设备号，[value]为对应新应用程序版本号
  Map<int, String> versions;

  /// 是否为连接层设备
  bool isLinkLayerDevice = false;

  /// 连接层设备连接超时时间
  int linkLayerConnectionTimeoutMills = 20000;

  /// 等待设备升级时间
  /// 即在文件传输完成后，等待多长时间开始重连或查询版本号
  int waiting4UpdateMills = 25000;

  /// 设备升级最大时间
  /// 即在文件传输完成后，等待超过多长时间后仍未成功则认为失败
  int maxWait4UpdateMills = 50000;

  /// 查询版本号重试间隔
  int queryVersionIntervalMills = 200;

  /// 设备正在请求升级
  bool deviceRequestingUpdate = false;

  /// 其他参数
  Map<String, dynamic>? extraParams;

  SingleDeviceUpdateConfig(
      this.index, this.device, this.localFile, this.versions,
      {this.remoteFilename = _defaultRemoteUpdateFileName});

  void autoSetupTimeoutMills(ProductModel model) {
    if (model.isLorinScooter()) {
      if (model.isLC1Series()) {
        if (device == DeviceUtils.lorinScooterMcDevice) {
          waiting4UpdateMills = 25000;
        } else if (device == DeviceUtils.lorinScooterBtDevice) {
          waiting4UpdateMills = 30000;
          maxWait4UpdateMills = 60000;
        }
      } else if (model.isLE1Series()) {
        if (model.isLE1V1Series()) {
          // 好盈版本
          if (device == DeviceUtils.lorinScooterMcDevice) {
            waiting4UpdateMills = 10000;
          } else if (device == DeviceUtils.lorinScooterBtDevice) {
            waiting4UpdateMills = 10000;
            maxWait4UpdateMills = 20000;
          }
        } else {
          // 松正版本
          if (device == DeviceUtils.lorinScooterMcDevice) {
            waiting4UpdateMills = 6000;
          } else if (device == DeviceUtils.lorinScooterBtDevice) {
            waiting4UpdateMills = 6000;
            maxWait4UpdateMills = 20000;
          }
        }
      } else if (model.isLR1Series() || model.isRSVeloSeries()) {
        if (device == DeviceUtils.lorinLR1BtDevice) {
          waiting4UpdateMills = 3000;
        } else {
          waiting4UpdateMills = 10000;
          maxWait4UpdateMills = 20000;
        }
      } else {
        waiting4UpdateMills = 25000;
        maxWait4UpdateMills = 60000;
      }
    } else if (model.isLorinEuc()) {
      waiting4UpdateMills = 10000;
      maxWait4UpdateMills = 30000;

      if (device == DeviceUtils.lorinVHmicDevice) {
        waiting4UpdateMills = 25000;
        maxWait4UpdateMills = 35000;
      } else if (device == DeviceUtils.lorinVMcDevice) {
        waiting4UpdateMills = 10000;
        maxWait4UpdateMills = 30000;
      } else if (device == DeviceUtils.lorinVBtDevice) {
        waiting4UpdateMills = 50000;
        maxWait4UpdateMills = 120000;
      }

      if (model.isV11Series()) {
        if (device == DeviceUtils.lorinVBtDevice) {
          waiting4UpdateMills = 10000;
          maxWait4UpdateMills = 30000;
        }
      } else if (model.isV14Series()) {
        if ([
          DeviceUtils.lorinVBMS1Device,
          DeviceUtils.lorinVBMS2Device,
          DeviceUtils.lorinVBMS3Device,
          DeviceUtils.lorinVBMS4Device
        ].contains(device)) {
          waiting4UpdateMills = 10000;
          maxWait4UpdateMills = 50000;
        }
      } else if (model.isE20Series() || model.isV6Series()) {
        if (device == DeviceUtils.lorinVBtDevice) {
          waiting4UpdateMills = 5000;
          maxWait4UpdateMills = 10000;
        } else if (device == DeviceUtils.lorinVHmicDevice) {
          waiting4UpdateMills = 10000;
          maxWait4UpdateMills = 20000;
        }
      } else if(model.isP6Series()){
        if(device == DeviceUtils.lorinVBMS1Device || device == DeviceUtils.lorinVBMS2Device || device == DeviceUtils.lorinVBMS3Device || device == DeviceUtils.lorinVBMS4Device){
          waiting4UpdateMills = 180000;
          maxWait4UpdateMills = 300000;
        }
      }
    } else if (model.isEzCANModel()) {
      waiting4UpdateMills = 5000;
    }
  }

  void setExtraParams(Map<String, dynamic>? params) {
    extraParams = params;
  }
}

/// 设备连接层处理器
abstract class LinkLayerDeviceHandler {
  void disconnect();

  void connect(Function connectedCallback,
      {Duration scanTimeout = const Duration(seconds: 3),
      Duration connectTimeout = const Duration(seconds: 10)});

  void updateLinkConfig(Map<String, dynamic> params);
}

/// 手动干预升级流程处理器
abstract class RequiredManualOperationHandler {
  final ProductProperty productProperty;

  RequiredManualOperationHandler(this.productProperty);

  /// 是否需要等待用户手动处理
  /// - [updateConfig] 当前正在升级的设备更新配置信息
  bool isRequiredManualOperation(SingleDeviceUpdateConfig updateConfig) {
    /// EzCAN型号全部需要用户手动处理完成升级
    if (productProperty.model.isEzCANModel()) return true;
    if (productProperty.model.isLE1V1Series()) {
      /// 当前升级配置中，包含的固件对应的设备号列表
      Iterable<int> containedDevices = updateConfig.versions.keys;
      if (containedDevices
          .contains(DeviceUtils.getMcDevice(productProperty.model))) {
        /// V1版本的LE1驱动板升级需要用户手动处理
        return true;
      }
    } else if (productProperty.model.isLR1Series() ||
        productProperty.model.isRSVeloSeries()) {
      /// 当前升级配置中，包含的固件对应的设备号列表
      Iterable<int> containedDevices = updateConfig.versions.keys;
      if (containedDevices.contains(DeviceUtils.lorinLR1BtDevice)) {
        /// LR1显示板升级需要用户手动处理
        return true;
      }
    }
    return false;
  }

  /// 等待用户操作
  /// - [f] 用户响应回调，附带bool参数，表示是否继续后续操作
  void waitingOperation(Function f);
}

/// 设备固件状态
enum FirmwareState {
  /// 正常
  ok,

  /// 上次固件更新被中断
  lastUpdateInterrupted,

  /// 需要强制更新
  needForceUpdate,

  /// 固件已损坏，需要更新
  abnormal;
}
